package cn.lg.soar.common.util.reflect;

import cn.lg.soar.common.util.asserts.InnerAssert;
import org.springframework.util.ConcurrentReferenceHashMap;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Predicate;

/**
 * 字段反射工具
 * @author luguoxiang
 * @date 2022/2/17
 * 开源项目：https://gitee.com/lgx1992/lg-soar 求star！请给我star！请帮我点个star！
 */
class FieldUtils {

    private final static Map<Class<?>, Map<String, Field>> FIELD_CACHE = new ConcurrentReferenceHashMap<>(128);
    /**
     * 设置值
     * @param object
     * @param fieldName
     * @param value
     * @throws IllegalAccessException
     */
    static void setValue(Object object, String fieldName, Object value) throws IllegalAccessException {
        InnerAssert.notEmpty(fieldName, "fieldName cannot be blank");
        String[] link = fieldName.split("\\.");
        int len = link.length - 1;
        // 查找对象
        for (int i = 0; i < len; i++) {
            Field field = privateGetField(object.getClass(), link[i]);
            field.setAccessible(true);
            object = field.get(object);
            field.setAccessible(false);
        }
        // 设置值
        Field field = privateGetField(object.getClass(), link[len]);
        field.setAccessible(true);
        field.set(object, value);
        field.setAccessible(false);
    }

    /**
     * 获取值
     * @param object
     * @param fieldName
     * @return
     * @throws IllegalAccessException
     */
    static Object getValue(Object object, String fieldName) throws IllegalAccessException {
        InnerAssert.notEmpty(fieldName, "fieldName cannot be blank");
        String[] link = fieldName.split("\\.");
        for (String name : link) {
            Field field = privateGetField(object.getClass(), name);
            field.setAccessible(true);
            object = field.get(object);
            field.setAccessible(false);
            if (object == null) {
                return null;
            }
        }
        return object;
    }

    /**
     * 获取字段
     * @param clazz
     * @param fieldName
     * @return
     */
    static Field getField(Class<?> clazz, String fieldName) {
        InnerAssert.notEmpty(fieldName, "fieldName cannot be blank");
        Map<String, Field> fieldMap = FIELD_CACHE.computeIfAbsent(clazz, c -> new HashMap<>(16));
        return fieldMap.computeIfAbsent(fieldName, f -> {
            String[] link = fieldName.split("\\.");
            Class<?> c = clazz;
            Field field = null;
            for (String name : link) {
                field = privateGetField(c, name);
                c = field.getType();
            }
            return field;
        });
    }

    /**
     * 扫描非静态字段
     * @param clazz
     * @param consumer
     * @param predicate
     */
    static void scan(Class<?> clazz, Consumer<Field> consumer, Predicate<Class<?>> predicate) {
        if (clazz == null) {
            return;
        }
        if (predicate.test(clazz)) {
            Field[] fields = clazz.getDeclaredFields();
            for (Field field : fields) {
                if (Modifier.isStatic(field.getModifiers())) {
                    continue;
                }
                consumer.accept(field);
            }
            scan(clazz.getSuperclass(), consumer, predicate);
        }
    }

    /**
     * 扫描所有字段
     * @param clazz
     * @param consumer
     * @param predicate
     */
    static void scanAllField(Class<?> clazz, Consumer<Field> consumer, Predicate<Class<?>> predicate) {
        if (clazz == null) {
            return;
        }
        if (predicate.test(clazz)) {
            Field[] fields = clazz.getDeclaredFields();
            for (Field field : fields) {
                consumer.accept(field);
            }
            scanAllField(clazz.getSuperclass(), consumer, predicate);
        }
    }

    /**
     * 递归获取字段
     * @param clazz
     * @param fieldName
     * @return
     */
    private static Field privateGetField(Class<?> clazz, String fieldName) {
        Field field = recursion(clazz, fieldName);
        if (field == null) {
            throw new RuntimeException(clazz.getName() + " not found field " + fieldName);
        }
        return field;
    }

    private static Field recursion(Class<?> clazz, String fieldName) {
        if (Object.class.equals(clazz)) {
            return null;
        }

        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            if (field.getName().equals(fieldName)) {
                return field;
            }
        }
        return recursion(clazz.getSuperclass(), fieldName);
    }

}
